// Copyright 2010, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.adwords.lib;

import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

/**
 * The class {@code ReportDate} represents the midnight-to-midnight time
 * period as observed by the AdWords server. This is the finest granularity for
 * representing start and end dates.
 * <p>
 * {@code ReportDate} allows you to specify a start or end date without
 * worrying about issues of time zone or daylight savings time.
 */
public final class ReportDate implements Comparable<ReportDate> {
  private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
  private final int year;
  private final int month;
  private final int date;
  private String string;

  /**
   * Returns a four digit year value.
   *
   * @return a four digit year value
   */
  public int getYear() {
    return year;
  }

  /**
   * Returns a month (0 = January, 11 = December) value.
   *
   * @return a month value
   */
  public int getMonth() {
    return month;
  }

  /**
   * Returns a day of the month value.
   *
   * @return a day of the month value
   */
  public int getDate() {
    return date;
  }

  /** Creates a {@code ReportDate} object representing today. */
  public ReportDate() {
    this(new GregorianCalendar(GMT));
  }

  /**
   * Creates a {@code ReportDate} object, given a {@code Calendar}.
   *
   * @param calendar a {@code Calendar} to use for creating
   *                 {@code ReportDate}
   */
  ReportDate(Calendar calendar) {
    year = calendar.get(Calendar.YEAR);
    month = calendar.get(Calendar.MONTH);
    date = calendar.get(Calendar.DATE);
    string = DateFormat.getDateInstance().format(toCalendar().getTime());
  }

  /**
   * Creates a {@code ReportDate} object, given a year, month, and a date.
   *
   * @param year a four digit year value
   * @param month a month value (0-indexed)
   * @param date a day of the month value
   * @throws IllegalArgumentException if the arguments do not represent a valid
   *     date
   */
  public ReportDate(int year, int month, int date) {
    this.year = year;
    this.month = month;
    this.date = date;
    Calendar calendar = toCalendar();
    calendar.setLenient(false);
    string = DateFormat.getDateInstance().format(calendar.getTime());
  }

  /**
   * Returns a {@code Calendar} object representing the date specified by
   * this {@code ReportDate}.
   *
   * @return a {@code Calendar} version of this {@code ReportDate}
   */
  public Calendar toCalendar() {
    final Calendar cal = new GregorianCalendar(GMT);
    cal.set(year, month, date, 12, 0);
    return cal;
  }

  /**
   * Returns a {@code Date} object representing the date specified by this
   * {@code ReportDate}.
   *
   * @return a {@code Date} version of this {@code eportDate}
   */
  public Date toDate() {
    return toCalendar().getTime();
  }

  /**
   * Returns a string representing the date in the current locale.
   *
   * @return a date formatted as a {@code String} in the default format
   */
  @Override
  public String toString() {
    return string;
  }

  /**
   * Returns whether two {@code ReportDate}s represent the same date.
   *
   * @param obj an {@code Object} to check for equality
   * @return true if {@code Object} is a {@code ReportDate} with the
   *     same year, month, and date as {@code this}
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }

    if (!(obj instanceof ReportDate)) {
      return false;
    }

    ReportDate reportDate = (ReportDate) obj;

    return (year == reportDate.year && month == reportDate.month &&
            date == reportDate.date);
  }

  /**
   * Returns a hash code representation.
   *
   * @return a hash code representation
   */
  @Override
  public int hashCode() {
    return (((87 * 23 + date) * 23) + year) * 23 + month;
  }

  /**
   * Returns a status of comparing two {@code ReportDate} objects. A
   * {@code ReportDate} is less than another if it represents a date that
   * is earlier, greater than if it represents a date that is later, and
   * equal otherwise.
   *
   * @param reportDate a {@code ReportDate} object to compare
   *     {@code this} to
   * @return a negative number if {@code this} is earlier than
   *     {@code obj}, 0 if {@code this} is equal to
   *     {@code obj}, a positive number otherwise
   * @throws ClassCastException if {@code obj} is not of
   *     {@code ReportDate} type
   */
  public int compareTo(ReportDate reportDate) {
    int delta = year - reportDate.year;
    if (delta != 0) {
      return delta;
    }

    delta = month - reportDate.month;
    if (delta != 0) {
      return delta;
    }

    return date - reportDate.date;
  }
}
